Skip to content

fix(rules): prove ContainerCpuThrottled can fire, and say that it cannot (#185) - #318

Merged
Gerrrt merged 1 commit into
mainfrom
gerrrt/cpu-throttle-inert-185
Sep 4, 2026
Merged

fix(rules): prove ContainerCpuThrottled can fire, and say that it cannot (#185)#318
Gerrrt merged 1 commit into
mainfrom
gerrrt/cpu-throttle-inert-185

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Closes #185, taking the path the issue argues for rather than the one that would make the rule live.

Not adding a CPU quota

The tempting fix is the wrong one.

Agreed, and the reasoning is now in the rule itself rather than only in the issue: the stack peaks at ~0.4 of 4 cores, HostHighLoad already fires at load15 > 2 x cores, and a quota tight enough to matter would be saturated inside individual 100ms CFS windows during a scrape burst — manufacturing alert fatigue to justify a control that prevents nothing.

The rule says what it is

A comment stating it is inert, why a quota is the wrong answer, and exactly what would make it live: any service gaining cpus: or deploy.resources.limits.cpus. Nothing else. It is kept against the day a quota is set for some other reason — a noisy-neighbour guest, a service that genuinely needs bounding.

The tests are what make keeping it safe

Per the convention in containers.test.yaml's header, a firing case paired with a quiet one:

  • Fires — 600 CFS periods a minute (the default 100ms period), 180 of them throttled: 30% against a 0.25 threshold. Realistic rather than a contrived 100%.
  • Quiet — both counters flat at 0, which is exactly what cAdvisor reports for every container in this estate and will keep reporting until something sets a quota. clamp_min holds the denominator at 1, so this evaluates to 0 rather than NaN.

Mutation-tested rather than trusted. A green suite proves nothing on its own — that is the #63 lesson. Both of these fail it:

Mutation Result
and container_spec_cpu_quota > 0 appended — the exact #63 shape, a guard on a series production reports as 0 caught
Threshold raised to > 0.95, above the fixture caught

One correction from writing them: I had the for: arithmetic wrong, assuming rate() waits for its 15m window to fill. It returns a value from the second sample, so the alert is pending at 1m and fires at 31m, not 45m. promtool caught it, and the paired 25m case now asserts the for: rather than assuming it.

Two knock-ons

Checks

validate.sh — 48 PASS, 0 FAIL, 2 SKIP. promtool test rules now covers 9 files in observability, up from 8.

🤖 Generated with Claude Code

…not (#185)

The rule has never been able to fire. CFS accounting only exists when a CPU
quota exists, no service sets one, so both counters sit at 0 forever and the
numerator can never be non-zero. `promtool check rules` passes it — it parses
PromQL and never asks whether an expression can be true — and it shows loaded
and healthy on the status page. That is #63 exactly, one rule up in the same
file.

Not fixed by adding `cpus:`, for the reasons #185 sets out: the stack peaks at
~0.4 of 4 cores, HostHighLoad already covers saturation at load15 > 2 x cores,
and a quota tight enough to matter would be saturated inside individual 100ms
CFS windows during a scrape burst — manufacturing alert fatigue to justify a
control that prevents nothing.

So it is kept and made honest instead. The rule now says it is inert, why a
quota is the wrong answer, and exactly what would change that: any service
gaining `cpus:` or `deploy.resources.limits.cpus`.

The tests are what make that safe. A firing case against synthetic CFS series
— 600 periods a minute at the default 100ms period, 180 throttled, 30% against
a 0.25 threshold — and a quiet case with both counters flat at 0, which is what
cAdvisor actually reports here. Mutation-tested rather than trusted: adding
`and container_spec_cpu_quota > 0` (the #63 shape) and raising the threshold
past the fixture both fail the suite.

`rate()` returns a value from the second sample rather than waiting for its 15m
window, so the alert fires at 31m and not 45m. promtool caught that; the paired
25m case now asserts the `for:` rather than assuming it.

observability.md's coverage counts moved with the new test, 27 to 28 tested and
29 to 28 not. check_docs.py caught that. The same paragraph still told authors
to keep each count on one line because the checker read prose line by line —
untrue since #209 made it whole-file — so that advice is removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Gerrrt
Gerrrt merged commit a6c3c8e into main Sep 4, 2026
3 checks passed
@Gerrrt
Gerrrt deleted the gerrrt/cpu-throttle-inert-185 branch September 4, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ContainerCpuThrottled cannot fire — no service sets a CPU quota

1 participant